// 创建 WxRequest 类
// 通过类的方式来进行封装，会让代码更加具有复用性
// 也可以方便添加新的属性和方法

class WxRequest {
    // 定义实例属性，用来设置默认请求参数
    defaults = {
      baseURL: '', // 请求基准地址
      url: '', // 接口的请求路径
      data: null, // 请求参数
      method: 'GET', // 默认的请求方法
      // 请求头
      header: {
        'Content-type': 'application/json' // 设置数据的交互格式
      },
      timeout: 60000, // 默认的超时时长，小程序默认的超时时长是 1 分钟
      isLoading: true // 控制是否使用默认的 loading，默认值是 true 表示使用默认的 loading
    }
  
    // 定义拦截器对象
    // 需要包含请求拦截器以及响应拦截器，方便在请求之前以及响应以后时进行逻辑处理
    interceptors = {
      // 请求拦截器
      // 在请求发送之前，对请求参数进行新增或者修改
      request: (config) => config,
  
      // 响应拦截器
      // 在服务器响应数据以后，对服务器响应的数据进行逻辑处理
      response: (response) => response
    }
  
    // 定义数组队列
    // 初始值需要是一个空数组，用来存储请求队列、存储请求标识
    queue = []
  
    // 用于创建和初始化类的属性以及方法
    // 在实例化时传入的参数，会被 constructor 形参进行接收
    constructor(params = {}) {
      // 通过 Object.assign 方法合并请求参数
      // 注意：需要传入的参数，覆盖默认的参数，因此传入的参数需要放到最后
      this.defaults = Object.assign({}, this.defaults, params)
    }
  
    // request 实例方法接收一个对象类型的参数
    // 属性值和 wx.request 方法调用时传递的参数保持一致
    request(options) {
      // 如果有新的请求，就清除上一次的定时器
      this.timerId && clearTimeout(this.timerId)
  
      // 注意：需要先合并完整的请求地址 (baseURL + url)
      // https://gmall-prod.atguigu.cn/mall-api/index/findBanner
      options.url = this.defaults.baseURL + options.url
  
      // 合并请求参数
      options = { ...this.defaults, ...options }
  
      // 在请求发送之前，添加 loading 效果
      // wx.showLoading()
  
      if (options.isLoading && options.method !== 'UPLOAD') {
        // 判断 queue 队列是否为空，如果是空，就显示 loading
        // 如果不是空，就不显示 loading，不调用 wx.showLoading()
        this.queue.length === 0 && wx.showLoading()
  
        // 然后立即向 queue 数组队列中添加请求标识
        // 每个标识代表是一个请求，标识是自定义的
        this.queue.push('request')
      }
  
      // 在请求发送之前，调用请求拦截器，新增和修改请求参数
      options = this.interceptors.request(options)
  
      // 需要使用 Promise 封装 wx.request，处理异步请求
      return new Promise((resolve, reject) => {
        if (options.method === 'UPLOAD') {
          wx.uploadFile({
            ...options,
  
            success: (res) => {
              // 需要将服务器返回的 JSON 字符串 通过 JSON.parse 转成对象
              res.data = JSON.parse(res.data)
  
              // 合并参数
              const mergeRes = Object.assign({}, res, {
                config: options,
                isSuccess: true
              })
  
              resolve(this.interceptors.response(mergeRes))
            },
  
            fail: (err) => {
              // 合并参数
              const mergeErr = Object.assign({}, err, {
                config: options,
                isSuccess: false
              })
  
              reject(this.interceptors.response(mergeErr))
            }
          })
        } else {
          wx.request({
            ...options,
  
            // 当接口调用成功时会触发 success 回调函数
            success: (res) => {
              // 不管是成功响应还是失败响应，都需要调用响应拦截器
              // 响应拦截器需要接收服务器响应的数据，然后对数据进行逻辑处理，处理好以后进行返回
              // 然后在通过 resolve 将返回的数据抛出去
  
              // 在给响应拦截器传递参数时，需要将请求参数也一起传递
              // 方便进行代码的调试或者进行其他逻辑处理，需要先合并参数
              // 然后将合并的参数传递给响应拦截器
  
              // 不管是请求失败还是请求成功，都已经将响应的数据传递给了响应拦截器
              // 这时候在合并参数的时候，追加一个属性：isSuccess
              // 如果属性值为 true，说明执行了 success 回调函数
              // 如果属性值为 false，说明执行了 fail 回调函数
              const mergeRes = Object.assign({}, res, {
                config: options,
                isSuccess: true
              })
              resolve(this.interceptors.response(mergeRes))
            },
  
            // 当接口调用失败时会触发 fail 回调函数
            fail: (err) => {
              // 不管是成功响应还是失败响应，都需要调用响应拦截器
              const mergeErr = Object.assign({}, err, {
                config: options,
                isSuccess: false
              })
              reject(this.interceptors.response(mergeErr))
            },
  
            // 接口调用结束的回调函数（调用成功、失败都会执行）
            complete: () => {
              if (options.isLoading) {
                // 在每一个请求结束以后，都会执行 complete 回调函数
                // 每次从 queue 队列中删除一个标识
                this.queue.pop()
  
                this.queue.length === 0 && this.queue.push('request')
  
                this.timerId = setTimeout(() => {
                  this.queue.pop()
  
                  // 在删除标识以后，需要判断目前 queue 数组是否为空
                  // 如果是空，说明并发请求完成了
                  // 就需要隐藏 loading，要调用 wx.hideLoading()
                  this.queue.length === 0 && wx.hideLoading()
  
                  clearTimeout(this.timerId)
                }, 1)
  
                // 不管请求时成功还是失败，都需要隐藏 loading
                // wx.hideLoading()
              }
            }
          })
        }
      })
    }
  
    // 封装 GET 实例方法
    get(url, data = {}, config = {}) {
      // 需要调用 request 请求方法发送请求，只需要组织好参数，传递给 request 请求方法即可
      // 当调用 get 方法时，需要将 request 方法的返回值 return 出去
      return this.request(Object.assign({ url, data, method: 'GET' }, config))
    }
  
    // 封装 DELETE 实例方法
    delete(url, data = {}, config = {}) {
      return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
    }
  
    // 封装 POST 实例方法
    post(url, data = {}, config = {}) {
      return this.request(Object.assign({ url, data, method: 'POST' }, config))
    }
  
    // 封装 PUT 实例方法
    put(url, data = {}, config = {}) {
      return this.request(Object.assign({ url, data, method: 'PUT' }, config))
    }
  
    // 用来处理并发请求
    all(...promise) {
      // 通过展开运算符接收传递的参数
      // 那么展开运算符会将传入的参数转成数组
      // console.log(promise)
  
      return Promise.all(promise)
    }
  
    /**
     * @description upload 实例方法，用来对 wx.uploadFile 进行封装
     * @param {*} url 文件的上传地址、接口地址
     * @param {*} filePath 要上传的文件资源路径
     * @param {*} name 文件对应的 key
     * @param {*} config 其他配置项
     */
    upload(url, filePath, name = 'file', config = {}) {
      return this.request(
        Object.assign({ url, filePath, name, method: 'UPLOAD' }, config)
      )
    }
  }
  
  export default WxRequest
  